package com.dss.sdk.utils.crypt;

import com.dss.sdk.enums.SignDataEnum;
import com.dss.sdk.enums.SignTypeEnum;
import com.dss.sdk.utils.date.DateUtil;
import com.dss.sdk.utils.json.GsonUtil;
import com.dss.sdk.utils.random.RandomUtil;
import com.dss.sdk.utils.string.StrUtil;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

/**
 * @author Fadada
 */
public class DSSCryptUtil {
    private static final Logger log = LoggerFactory.getLogger(DSSCryptUtil.class);
    private static final Charset UTF8 = StandardCharsets.UTF_8;

    private DSSCryptUtil() {
    }

    public static byte[] hmac256(byte[] key, String msg) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        mac.init(secretKeySpec);
        return mac.doFinal(msg.getBytes(UTF8));
    }

    public static byte[] hmacSM3(byte[] key, String msg) {
        HMac mac = new HMac(new SM3Digest());
        KeyParameter keyParameter = new KeyParameter(key);
        mac.init(keyParameter);
        mac.update(msg.getBytes(UTF8), 0, msg.length());
        byte[] result = new byte[mac.getMacSize()];
        mac.doFinal(result, 0);
        return result;
    }

    public static String sha256Hex(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] d = md.digest(s.getBytes(UTF8));
        return HexUtil.printHexBinary(d).toLowerCase();
    }

    /**
     * @param signType  签名方式
     * @param sortParam 排序后得参数字符串
     * @param timestamp 时间戳
     * @param appSecret 应用秘钥
     * @return 签名值
     * @throws Exception 异常
     */
    public static String sign(String sortParam, String signType, String timestamp, String appSecret) {
        String sign = StrUtil.EMPTY;
        try {
            //将排序后字符串转为sha256Hex
            String signText = sha256Hex(sortParam);
            SignTypeEnum signEnum = SignTypeEnum.getAlgEnum(signType);
            byte[] secretSigning;
            switch (signEnum) {
                case SM3:
                    //计算签名
                    secretSigning = hmacSM3(appSecret.getBytes(UTF8), timestamp);
                    //计算后得到签名
                    sign = HexUtil.printHexBinary(hmacSM3(secretSigning, signText)).toLowerCase();
                    break;
                case SHA256:
                    //计算签名
                    secretSigning = hmac256(appSecret.getBytes(UTF8), timestamp);
                    //计算后得到签名
                    sign = HexUtil.printHexBinary(hmac256(secretSigning, signText)).toLowerCase();
                    break;
            }
        } catch (Throwable e) {
            log.error("签名计算异常", e);
        }
        return sign;
    }


    public static String sortParameters(Map<String, String> parameters) {
        if (parameters.isEmpty()) {
            return null;
        }
        List<String> removeKeys = new ArrayList<String>() {{
            add(SignDataEnum.LANGUAGE.getName());
        }};
        for (Map.Entry<String, String> entry : parameters.entrySet()) {
            if (StrUtil.isBlank(entry.getValue())) {
                removeKeys.add(entry.getKey());
            }
        }
        for (String key : removeKeys) {
            parameters.remove(key);
        }
        StringBuilder stringBuilder = new StringBuilder();
        SortedMap<String, String> paramMap = new TreeMap<>(parameters);
        int index = 0;
        for (Map.Entry<String, String> entry : paramMap.entrySet()) {
            stringBuilder.append(entry.getKey()).append("=").append(entry.getValue());
            index++;
            if (index != parameters.size()) {
                stringBuilder.append("&");
            }
        }
        return stringBuilder.toString();
    }

    public static HashMap<String, String> getHeaderMap(String appId, String signType, String language) {
        HashMap<String, String> reqHeader = new HashMap<>(8);
        reqHeader.put(SignDataEnum.APPID.getName(), appId);
        reqHeader.put(SignDataEnum.SIGNTYPE.getName(), signType);
        reqHeader.put(SignDataEnum.LANGUAGE.getName(), language);
        reqHeader.put(SignDataEnum.TIMESTAMP.getName(), String.valueOf(DateUtil.current()));
        reqHeader.put(SignDataEnum.NONCE.getName(), RandomUtil.randomString(32));
        return reqHeader;
    }

    public static HashMap<String, String> getHeaderMap(String appId, String language) {
        return getHeaderMap(appId, SignTypeEnum.SHA256.getValue(), language);
    }

    public static void getFormSign(Map<String, String> reqHeader, Map<String, String> params, String appKey) {
        if (params == null) {
            params = new HashMap<>(8);
        }
        TreeMap<String, String> sortMap = new TreeMap<>(params);
        String bizContent = GsonUtil.toJsonStr(sortMap);
        getSign(reqHeader, bizContent, appKey);
    }

    public static void getSign(Map<String, String> reqHeader, String json, String appKey) {
        signature(reqHeader, json, appKey);
    }

    private static void signature(Map<String, String> reqHeader, String biz, String appKey) {
        reqHeader.put(SignDataEnum.BIZCONTENT.getName(), biz);
        //排序后的参数字符串
        String paramToSignStr = DSSCryptUtil.sortParameters(reqHeader);
        //签名计算
        String signature = DSSCryptUtil.sign(paramToSignStr, reqHeader.get(SignDataEnum.SIGNTYPE.getName()),
                reqHeader.get(SignDataEnum.TIMESTAMP.getName()), appKey);
        //赋值
        reqHeader.put(SignDataEnum.SIGN.getName(), signature);
        reqHeader.remove(SignDataEnum.BIZCONTENT.getName());
    }
}
